package vn.evolus.pencil.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.DispatcherServlet;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.xml.sax.SAXException;

import vn.evolus.pencil.model.Collection;
import vn.evolus.pencil.model.User;
import vn.evolus.pencil.service.ItemCollectionService;
import vn.evolus.pencil.service.UploadDataService;
import vn.evolus.pencil.service.UserService;
import vn.evolus.pencil.service.impl.UploadDataServiceImpl;
import vn.evolus.pencil.utils.MappingUtil;
import vn.evolus.pencil.viewmodel.CollectionViewModel;
import vn.evolus.pencil.viewmodel.ItemCollections;


@Controller
@RequestMapping(value = "/collection")
public class CollectionController {
	private static final Logger logger = LoggerFactory.getLogger(CollectionController.class);
	
	@Autowired
	private ItemCollectionService itemCollectionService;
	
	@Autowired
	private UserService userService;
	
	@Autowired
	private UploadDataService uploadService;
	
	@RequestMapping(value = "/getAllCollections", method = RequestMethod.GET)
	@ResponseBody
	public ItemCollections getAllItems(String start, String count, HttpServletRequest request){
		
		ItemCollections result = new ItemCollections();
		List<Collection> listItems = itemCollectionService.getAllItemCollections();
		int collectionsCount = Integer.parseInt(count);
		int firstIndex = getFirstIndex(Integer.parseInt(start), listItems.size() - 1, collectionsCount);
		int lastIndex = listItems.size() - firstIndex < collectionsCount ? listItems.size() : firstIndex + collectionsCount;
		
		List<Collection> subList = listItems.subList(firstIndex, lastIndex);
		List<CollectionViewModel> listItemsVM = new ArrayList<CollectionViewModel>();
		
		String contextUrl = getContextUrl(request); 
		
		
		for (Collection collection : subList) {
			listItemsVM.add(MappingUtil.mappingCollection(collection, contextUrl));
		}
		
		result.setItemCollections(listItemsVM);
		result.setStart(firstIndex);
		result.setMax(listItems.size());
		
		return result;
	}
	
	private int getFirstIndex(int index, int size, int count) {
		int newIndex = index;
		while(newIndex > size && newIndex > 0) {
			newIndex = newIndex - count;
		}
		return newIndex;
	}
	
	@RequestMapping(value = "/uploadCollection", method = RequestMethod.GET)
	public String getCollection(Model model, HttpServletRequest request){
		String contextUrl = getContextUrl(request);
		
		List<CollectionViewModel> listItemsVM = getCollectionsByUser(userService.getCurrentUser(), contextUrl);		
		model.addAttribute("collections", listItemsVM);	
		return "collection/uploadCollection";
	}
	
	@RequestMapping(value = "/deleteCollection", method = RequestMethod.GET)
	public String deleteCollection(Model model, String id, HttpServletRequest request){
		String resultMessage = "";
		User user = userService.getCurrentUser();
		String contextUrl = getContextUrl(request);
		
		for (Collection collection : user.getCollections()){
			if (collection.getId() == Long.parseLong(id)){
				if(deleteFileFromServer(uploadService.getUploadFolder()+collection.getPackageUrl())) {
					if(collection.getIcon() != null) deleteFileFromServer(uploadService.getUploadFolder()+collection.getIcon());
					user.getCollections().remove(collection);
					userService.updateUser(user);
				} else {
					resultMessage = "* Deleted collection fail, please try again!";
				}
				break;
			}
		}
		if(resultMessage.equals("")) {
			return "redirect:uploadCollection";
		} else {
			model.addAttribute("deleteError", resultMessage);
			List<CollectionViewModel> listItemsVM = getCollectionsByUser(userService.getCurrentUser(), contextUrl);		
			model.addAttribute("collections", listItemsVM);	
			return "collection/uploadCollection";
		}
	}
	
	private boolean deleteFileFromServer(String url) {
		boolean result = true;
		try {
			File file = new File(url);
			result = file.delete();
		} catch (Exception ex) {
			
			result = false;
		}
		return result;
	}
	
	@RequestMapping(value = "/uploadCollection", method = RequestMethod.POST)
	public String uploadCollection(MultipartHttpServletRequest request, @RequestParam("collectionFile") MultipartFile collectionFile, @RequestParam("imageFile") MultipartFile imageFile, Model model){
		String resultMessage = "";
		if (!collectionFile.isEmpty() && UploadDataServiceImpl.zipMineTypes.contains(collectionFile.getContentType())) {
			String collectionFileName = UUID.randomUUID().toString() +".zip";
			String collectionPath = uploadService.getUploadFolder() + collectionFileName;
			String[] result = validateCollectionZipFile(collectionFile, uploadService.getUploadFolder() + collectionFileName);
			
			if(result[0] == null) {
				//uploaded successful
				String collectionId = result[1];

				if (duplicateCollectionId(collectionId)) {
					deleteFileFromServer(collectionPath);
					resultMessage = "* It's a duplicate collection id in our repository!";
				} else {
					Collection item = new Collection(); 
					
					if (!imageFile.isEmpty() && imageFile.getContentType().startsWith("image")) {
						boolean uploadImageResult = false;
						String imageFileName = UUID.randomUUID().toString() + getExtensionName(imageFile.getOriginalFilename());
						String imagePath = uploadService.getUploadFolder() + imageFileName;
						uploadImageResult = uploadFile(imageFile, imagePath);
						if (uploadImageResult) {
							item.setIcon(imageFileName);
						}
					}
					
					item.setCollectionId(collectionId);
					item.setName(result[2]);
					if(result[3] != null) {
						item.setDescription(result[3]);
					}
					if(result[4] != null) {
						item.setAuthor(result[4]);
					} else {
						item.setAuthor(userService.getCurrentUser().getDisplayName());
					}					
					item.setUser(userService.getCurrentUser());
					item.setLastUpdate(new Date());
					item.setPackageUrl(collectionFileName);
					
					try {
						itemCollectionService.saveItemCollection(item);
					} catch (Exception e) {
						resultMessage = "* Uploaded collection fail, please try again!";
					}
				}
			} else {
				resultMessage = "* Error: " + result[0];
			}
		} else {
			resultMessage = "* Please input valid collection zip file";
		}
		
		if(resultMessage.equals("")) {
			return "redirect:uploadCollection";
		} else {
			String contextUrl = getContextUrl(request);
			model.addAttribute("uploadError", resultMessage);
			List<CollectionViewModel> listItemsVM = getCollectionsByUser(userService.getCurrentUser(), contextUrl);		
			model.addAttribute("collections", listItemsVM);	
			return "collection/uploadCollection";
		}
	}
	
	@RequestMapping(value = "/getIconImage", method = RequestMethod.GET)
	@ResponseBody
	public void getImage(HttpServletResponse response, Model model,	long id) throws IOException {
		try {
			String imageUrl = itemCollectionService.getCollectionIcon(id);
			if(imageUrl != null) {
				File imageFile = new File(imageUrl);
				InputStream inputStream = new FileInputStream(imageFile);		
				byte[] imageBytes = IOUtils.toByteArray(inputStream);
				
				response.setHeader("Content-Type", "image/png");
				response.setContentLength(imageBytes.length);
				response.getOutputStream().write(imageBytes);
				
				inputStream.close();
			}

		} catch (IOException e) {
			e.printStackTrace();
		}

	}
	
	@RequestMapping(value = "/getPackage", method = RequestMethod.GET)
	@ResponseBody
	public void getZipFile(HttpServletResponse response, Model model, long id) throws IOException {
		try {
			Collection collection = itemCollectionService.getCollectionById(id);
			if(collection != null) {
				String zipUrl = itemCollectionService.getCollectionZip(id);
				
				File zipFile = new File(zipUrl);
				InputStream inputStream = new FileInputStream(zipFile);		
				byte[] zipBytes = IOUtils.toByteArray(inputStream);
				
				response.setHeader("Content-Type", "application/zip");
				response.setContentLength(zipBytes.length);
				response.setHeader("Content-Disposition","attachment; filename=\"" + collection.getName() +".zip\"");
				response.getOutputStream().write(zipBytes);
				inputStream.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
	
	private boolean duplicateCollectionId(String collectionId) {
		boolean result = false;
		List<Collection> listItems = itemCollectionService.getAllItemCollections();
		for (Collection collection : listItems) {
			if(collection.getCollectionId().equals(collectionId)) return true;
		}
		return result;
	}
	
	private String getFileName(String file) {
		int lastDotIndex = file.lastIndexOf(".");
		if(lastDotIndex > -1) {
			return file.substring(0, lastDotIndex);
		} else {
			return file;
		}
	}
	
	private String getExtensionName(String file) {
		int lastDotIndex = file.lastIndexOf(".");
		if(lastDotIndex > -1) {
			return file.substring(lastDotIndex);
		} else {
			return file;
		}
	}
	
	private boolean uploadFile(MultipartFile file, String path) {
		boolean result = true;
		
		try {
			File destination = new File(path);
			file.transferTo(destination);
		} catch (IllegalStateException e) {
			e.printStackTrace();
			return false;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		
		return result;
	}	
	
	private String[] validateCollectionZipFile(MultipartFile originalFile, String path) {
		String[] result = new String[5];
		
		try {
			File destination = new File(path);
			originalFile.transferTo(destination);
			
			ZipFile zipFile = new ZipFile(destination);
			
			Enumeration entries = zipFile.entries();
			ZipEntry definitionEntry = null;
			ZipEntry dtdEntry = null;
			
			while(entries.hasMoreElements()) {
				ZipEntry entry = (ZipEntry)entries.nextElement();
				if(!entry.isDirectory() && entry.getName().equals("Definition.xml")){
					definitionEntry = entry;
				} else if (!entry.isDirectory() && entry.getName().equals("en-US.dtd")) {
					dtdEntry = entry;
				}
				if(definitionEntry != null && dtdEntry != null) break;
			}
			
			if(definitionEntry != null) {
				//merge dtd and definition file
				int BUFFER = 1024;
				int count;
				byte data[] = new byte[BUFFER];
				String strFileName = uploadService.getUploadFolder() + getFileName(originalFile.getOriginalFilename())+".xml";
				FileOutputStream fos = new FileOutputStream(strFileName);
				
				if(dtdEntry != null) {
					InputStream dtdContent = zipFile.getInputStream(dtdEntry);
					String str = "<!DOCTYPE Shapes [\n";
					fos.write(str.getBytes());
					while ((count = dtdContent.read(data, 0, BUFFER)) != -1) {
		               fos.write(data, 0, count);
		            }
					str = "\n]>\n";
					fos.write(str.getBytes());
				}
				
	            InputStream definitionContent = zipFile.getInputStream(definitionEntry);
				while ((count = definitionContent.read(data, 0, BUFFER)) != -1) {
	               fos.write(data, 0, count);
	            }
	            fos.close();
	            
	            //load xml file and get info
	            File definitionFile = new File(strFileName);
	            
	            DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
				DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();		
				Document xml = docBuilder.parse(definitionFile);
				
				NamedNodeMap attr = xml.getDocumentElement().getAttributes();
				result[1] = attr.getNamedItem("id").getNodeValue();
				result[2] = attr.getNamedItem("displayName").getNodeValue();
				if(attr.getNamedItem("description") != null) {
					result[3] = attr.getNamedItem("description").getNodeValue();
				}
				if(attr.getNamedItem("author") != null) {
					result[4] = attr.getNamedItem("author").getNodeValue();
				}
				definitionFile.delete();
			}
			zipFile.close();
			
			if(result[0] != null) {
				destination.delete();
			}
		} catch (ZipException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			result[0] = "ZipException";
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			result[0] = "IOException";
		} catch (IllegalStateException e) {
			e.printStackTrace();
			result[0] = "IllegalStateException";
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			result[0] = "ParserConfigurationException";
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			result[0] = "SAXException," +e.getMessage();
		}	
		
		return result;
	}
	
	private List<CollectionViewModel> getCollectionsByUser(User user, String contextPath) {
		
		Set<Collection> listItems = user.getCollections();
			
		List<CollectionViewModel> listItemsVM = new ArrayList<CollectionViewModel>();
		
		for (Collection collection : listItems) {
			listItemsVM.add(MappingUtil.mappingCollection(collection, ""));
		}
		
		return listItemsVM;
	}
	
	private String getContextUrl(HttpServletRequest request){
		StringBuffer fullUrl = request.getRequestURL();
		int index = fullUrl.lastIndexOf("/");
		
		String currentUrlController = fullUrl.substring(0, index);
		return currentUrlController;
	}
}
